The Vegetation Condition Index (VCI) expresses how “healthy” or
“stressed” vegetation is at a given time compared to its own historical minimum and
maximum NDVI values. It is widely used for drought monitoring,
crop stress detection, and early warning systems.
1. What is VCI?
VCI is a normalized index derived from NDVI, indicating the relative position of
current NDVI values between historical (multi-year) minimum and maximum for
the same pixel. Low VCI values indicate vegetation under stress (e.g. drought),
while high VCI values indicate favorable conditions.
Mathematical Definition
VCI is usually defined as:
VCI = (NDVIcurrent − NDVImin) / (NDVImax − NDVImin)
Dimensionless (0 to 1, often scaled 0–100)
Where:
NDVIcurrent: Current or recent NDVI (e.g. last dekad or month)
NDVImin: Historical minimum NDVI for each pixel over a reference period
NDVImax: Historical maximum NDVI for each pixel over the same period
Typical Interpretation (scaled 0–100)
VCI Range
Interpretation
0 – 20
Severe stress / extreme drought conditions
20 – 40
Moderate to high vegetation stress
40 – 60
Near normal vegetation condition
60 – 80
Good growing conditions, healthy vegetation
80 – 100
Very favorable conditions / exceptionally lush vegetation
Use: drought early warning • crop monitoring • seasonal assessments
2. Data & NDVI Time Series for VCI
Common Sensors & NDVI Source
Sentinel-2 (ESA) – 10 m
Red: B4 (~665 nm)
NIR: B8 (~842 nm)
NDVI derived as (NIR − Red) / (NIR + Red)
Landsat 8/9 OLI – 30 m
Red: B4
NIR: B5
NDVI derived similarly
Reference Period
VCI requires a multi-year NDVI time series to estimate
NDVI_min and NDVI_max for each pixel. Typical
reference windows:
At least 3–5 years of NDVI data
Consistent seasonal window (e.g. growing season months)
Good Practice
Use surface reflectance products with atmospheric correction.
Filter out cloudy scenes using cloud percentage and masks.
Compute NDVI composites (e.g. median) for specific periods (dekad, month, season).
Ensure the historical min/max are computed using comparable seasonal windows.
3. Google Earth Engine Code – VCI from Sentinel-2 NDVI
Steps: open code.earthengine.google.com → New Script → paste the code →
draw your AOI as geometry on the map → click Run.
The script builds a multi-year NDVI climatology, then computes VCI for a recent period
and exports it as GeoTIFF to Google Drive.
// VCI (Vegetation Condition Index) from Sentinel-2 NDVI
// -----------------------------------------------------
// This script:
// 1) Uses Sentinel-2 SR over a multi-year period to compute per-pixel NDVI_min & NDVI_max.
// 2) Computes a recent NDVI composite (target period).
// 3) Derives VCI = (NDVI_current - NDVI_min) / (NDVI_max - NDVI_min).
// 4) Displays and exports VCI as GeoTIFF.
// -------------------------------------------------------
// 1. Define Area of Interest (AOI)
// -------------------------------------------------------
var roi = geometry; // Make sure a 'geometry' object exists in the left panel
// Center the map on the AOI
Map.centerObject(roi, 8);
// -------------------------------------------------------
// 2. Define time ranges
// -------------------------------------------------------
// Reference period (for NDVI_min and NDVI_max climatology)
// You can adjust these years based on your study
var refStart = '2018-01-01';
var refEnd = '2023-12-31';
// Target (current) period for which you want VCI
var targetStart = '2023-06-01';
var targetEnd = '2023-08-31'; // example: a summer season window
// -------------------------------------------------------
// 3. Function to mask clouds (Sentinel-2 SR)
// -------------------------------------------------------
function maskS2clouds(image) {
var scl = image.select('SCL');
// Keep vegetation, bare soil, water etc. and remove clouds/shadows
var mask = scl.eq(4) // vegetation
.or(scl.eq(5)) // not vegetated
.or(scl.eq(6)) // water
.or(scl.eq(7)) // unclassified
.or(scl.eq(11)); // snow/ice (optional)
return image.updateMask(mask);
}
// -------------------------------------------------------
// 4. Build NDVI image collection for the reference period
// -------------------------------------------------------
var s2_ref = ee.ImageCollection('COPERNICUS/S2_SR')
.filterBounds(roi)
.filterDate(refStart, refEnd)
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 40))
.map(maskS2clouds)
.map(function (img) {
var ndvi = img.normalizedDifference(['B8', 'B4']).rename('NDVI');
return ndvi.copyProperties(img, img.propertyNames());
});
// Compute per-pixel NDVI_min and NDVI_max over the reference period
var ndviMin = s2_ref.reduce(ee.Reducer.min()).rename('NDVI_min');
var ndviMax = s2_ref.reduce(ee.Reducer.max()).rename('NDVI_max');
// -------------------------------------------------------
// 5. Compute NDVI for the target (current) period
// -------------------------------------------------------
var s2_target = ee.ImageCollection('COPERNICUS/S2_SR')
.filterBounds(roi)
.filterDate(targetStart, targetEnd)
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 40))
.map(maskS2clouds)
.map(function (img) {
var ndvi = img.normalizedDifference(['B8', 'B4']).rename('NDVI');
return ndvi.copyProperties(img, img.propertyNames());
});
// Use median NDVI over the target period
var ndviCurrent = s2_target.median().rename('NDVI_current');
// -------------------------------------------------------
// 6. Compute VCI
// -------------------------------------------------------
// Avoid division by zero: add a small epsilon to denominator
var epsilon = 1e-6;
var vci = ndviCurrent
.subtract(ndviMin)
.divide(ndviMax.subtract(ndviMin).add(epsilon))
.rename('VCI');
// Optionally scale to 0–100
var vciScaled = vci.multiply(100).rename('VCI_0_100');
// -------------------------------------------------------
// 7. Visualization
// -------------------------------------------------------
var vciVis = {
min: 0,
max: 100,
palette: [
'#4c0000', // very low VCI (severe stress)
'#b30000',
'#ff4d4d',
'#ffff66',
'#66ff66',
'#009933' // very high VCI (lush vegetation)
]
};
Map.addLayer(vciScaled.clip(roi), vciVis, 'VCI (0–100)', true);
// Also show a true color composite for context
var rgb = ee.ImageCollection('COPERNICUS/S2_SR')
.filterBounds(roi)
.filterDate(targetStart, targetEnd)
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 40))
.map(maskS2clouds)
.select(['B4', 'B3', 'B2']) // RGB
.median()
.clip(roi);
Map.addLayer(rgb, {min: 0, max: 3000}, 'True Color (RGB)', false);
// -------------------------------------------------------
// 8. Export VCI to Google Drive
// -------------------------------------------------------
Export.image.toDrive({
image: vciScaled.clip(roi),
description: 'VCI_Export',
fileNamePrefix: 'VCI_0_100',
folder: 'GEE_Exports',
scale: 20, // adjust based on resolution (10–30 m)
region: roi,
maxPixels: 1e13
});
// End of VCI script